var express = require('express');
var Excel = require("exceljs");
var path = require("path")
var fs = require("fs")
var os = require('os');
var uuid = require('uuid');
var router = express.Router();
var cache = require('../utils/cache')
const Bizdef = require('../models/Bizdef');
const Bizinst = require('../models/Bizinst');
const Bizrelainst = require('../models/Bizrelainst');
const Bizrela = require('../models/Bizrela');
const RelationType = require('../models/RelationType')
const Instance = require('../models/Instance');
const SchemaIcon = require('../models/SchemaIcon')
var formidable = require('formidable');
var fs = require('fs');
const mineType = require("mime-types");
var tools = require('../utils/tools')
var settings = require('../settings')
var changesets = require('diff-json');
var kafka = require('../utils/kafka')
var kafkaProducer = kafka.producer
var log = require('../utils/logs').getLogger();
var CACHE_INSTANCE = 'bizs'

/**
 * @swagger
 *
 * /cmdb/bizs/bizdef/{bizdef}/page:
 *   get:
 *     tags:
 *       - 业务实例管理
 *     summary: 分页查询模型实例
 *     description: 根据编码、名字、属性值模糊查询。支持根据属性key:value精确查询s_foo=bar
 *     parameters:
 *       - name: bizdef
 *         description: 业务模型ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: s_kw
 *         description: 关键字
 *         in: query
 *         required: false
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.get('/bizdef/:bizdef/page', async function (req, res, next) {

  var isById = tools.isValidObjectId(req.params.bizdef)
  var bizdefQuery = isById ? { _id: req.params.bizdef } : { code: req.params.bizdef }
  var bizdef = await Bizdef.findOne(bizdefQuery)
  if (!bizdef) {
    return tools.handleError(res, '业务模型不存在')
  }

  var keyword = req.query.s_kw || ""
  keyword = keyword.replace(/(^\s*)|(\s*$)/g, '')
  var query = getSearchQuery(keyword)
  query.bizdef = bizdef._id
  // 支持根据属性key:value精确查询
  Object.keys(req.query).filter(key => key !== 's_kw' && key.indexOf('s_') > -1).forEach(queryKey => {
    query.attrs = { "$elemMatch": { "key": queryKey.substring(2), value: req.query[queryKey] } }
  })

  Bizinst.page(query, req.query.page, req.query.size, result => {
    return res.json({ status: 'success', data: result.docs, pageable: result.pageable })
  })
});

function getSearchQuery (keyword) {
  if (!keyword) {
    return {}
  }
  var keywords = keyword.replace(/(\s+)/g, ' ').replace(/(^\s*)|(\s*$)/g, '').split(' ')
  var query = {
    "$and": []
  }
  keywords.forEach(kw => {
    var $or = {
      "$or": [
        { "name": new RegExp(kw, "i") },
        { "code": new RegExp(kw, "i") },
        {
          "attrs": {
            "$elemMatch": {
              "value": new RegExp(kw, "i")
            }
          }
        }]
    }
    query["$and"].push($or)
  })

  return query
}


/**
 * @swagger
 *
 * /cmdb/bizs:
 *   post:
 *     tags:
 *       - 业务实例管理
 *     summary: 创建实例
 *     description: 创建实例。属性按照{key1冒号value1, key2冒号value2,...}的形式提交
 *     parameters:
 *       - name: bizdef
 *         description: 业务模型ID
 *         in: formData
 *         required: true
 *         type: string
 *       - name: code
 *         description: 实例编码
 *         in: formData
 *         required: true
 *         type: string
 *       - name: name
 *         description: 实例名称
 *         in: formData
 *         required: true
 *         type: string
 *       - name: desc
 *         description: 实例描述
 *         in: formData
 *         required: false
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.post('/', async function (req, res, next) {
  var isById = tools.isValidObjectId(req.body.bizdef)
  var bizdefQuery = isById ? { _id: req.body.bizdef } : { code: req.body.bizdef }
  var bizdef = await Bizdef.findOne(bizdefQuery)

  if (!bizdef) return tools.handleError(res, '业务模型不存在');

  // 通过模型的属性定义，筛选传递的参数
  var attrs = []
  bizdef.attrs.forEach(a => {
    attrs.push({ key: a.field, value: req.body[a.field] })
  })

  // 包含的实例关系
  var instances = req.body.bizs_instances ? req.body.bizs_instances : []
  instances.forEach(b =>{
    const instance = Instance.findById(b)
    if (!instance) return tools.handleError(res, '业务实例包括的资源不存在');
  })

  var { code, name, desc } = req.body
  Bizinst.create({ name, desc, code, bizdef: bizdef._id, instances:instances, attrs }, function (err, doc) {
    if (err) {
      if (err.message.indexOf('duplicate key error') !== -1) {
        var msg = '唯一性约束错误'
        return tools.handleError(res, msg)
      }
      return tools.handleError(res, err)
    };
    return res.json({ status: 'success', data: doc })
  });
});

/**
 * @swagger
 *
 * /cmdb/bizs/{id}:
 *   get:
 *     tags:
 *       - 业务实例管理
 *     summary: 实例详情
 *     description: 实例详情。查询参数attrs可以使单个属性，数组，或逗号分隔。查询参数tags可以使单个属性，数组，或逗号分隔
 *     parameters:
 *       - name: id
 *         description: 实例ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: attrs
 *         description: 属性编码
 *         in: query
 *         required: false
 *         type: string
 *       - name: tags
 *         description: 标签编码
 *         in: query
 *         required: false
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.get('/:id', async function (req, res, next) {
  // 查询指定属性的属性值
  var attrs = req.query.attrs || []
  if (!tools.isArray(attrs)) {
    attrs = attrs.split(',');
  }
  // 查询指定Tag的属性值
  // var tags = req.query.tags || []
  // if (!tools.isArray(tags)) {
  //   tags = tags.split(',');
  // }

  var cached = await cache.exists(CACHE_INSTANCE, req.params.id)
  if (cached) {
    var doc = await cache.get(CACHE_INSTANCE, req.params.id)
    // if (tags.length > 0) {
    //   var schemadef = doc.schemadef._id
    //   var tagModels = await Tag.find({ schemadef, code: { $in: tags } })
    //   var tagAttrs = new Set()
    //   tagModels.forEach(m => {
    //     m.attrs.forEach(a => tagAttrs.add(a))
    //   })
    //   attrs = new Set([...attrs, ...tagAttrs])
    //   attrs = [...attrs]
    // }
    if (attrs.length > 0) {
      doc.attrs = doc.attrs.filter(it => attrs.includes(it.key))
    }
    return res.json({ status: 'success', data: doc })
  } else {
    var isById = tools.isValidObjectId(req.params.id)
    var query = isById ? { _id: req.params.id } : { code: req.params.id }
    var match = isById ? { _id: { $ne: req.params.id } } : { code: { $ne: req.params.id } }
    Bizinst.findOne(query)
      .populate({ path: 'bizdef', select: '-versions' })
      .populate({ path: 'relations', populate: { path: 'src', match: match, select: '-versions' } })
      .populate({ path: 'relations', populate: { path: 'dest', match: match, select: '-versions' } })
      .populate({ path: 'relations', populate: { path: 'rela' } })
      .populate({ path: 'instances', populate: { path: 'schemadef' }, select: '-versions' })
      .exec(async (err, doc) => {
        if (err) return tools.handleError(res, err);
        if (!doc) return tools.handleError(res, '业务实例不存在');

        // 模型属性
        if (!cached) {
          cache.set(CACHE_INSTANCE, req.params.id, doc, 6 * 1000)
        }
        // if (tags.length > 0) {
        //   var schemadef = doc.schemadef._id
        //   var tagModels = await Tag.find({ schemadef, code: { $in: tags } })
        //   var tagAttrs = new Set()
        //   tagModels.forEach(m => {
        //     m.attrs.forEach(a => tagAttrs.add(a))
        //   })
        //   attrs = new Set([...attrs, ...tagAttrs])
        //   attrs = [...attrs]
        // }
        if (attrs.length > 0) {
          doc.attrs = doc.attrs.filter(it => attrs.includes(it.key))
        }
        return res.json({ status: 'success', data: doc })
      })
  }
});

/**
 * @swagger
 *
 * /cmdb/bizs/{id}:
 *   delete:
 *     tags:
 *       - 业务实例管理
 *     summary: 删除业务实例
 *     description: 相关的关系同时会被删除
 *     parameters:
 *       - name: id
 *         description: 业务实例ID
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.delete('/:id', async function (req, res, next) {
  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }
  var inst = await Bizinst.findOne(query)
  if(!inst) {
    return tools.handleError(res, '业务实例不存在');
  }
  // 先查出所有的关系实例
  var allRelationInstance = await Bizrelainst.find({ $or: [{ src: inst._id }, { dest: inst._id }] })
  var allRelationInstanceId = allRelationInstance.map(it => it._id)
  // 先删除所有的关系实例
  await Bizrelainst.deleteMany({_id: {$in: allRelationInstanceId}})
  // 删除所有相关实例对关系的引用
  await Bizinst.updateMany({ relations: { $in: allRelationInstanceId } }, { $pull: { relations: { $in: allRelationInstanceId } } })
  // 删除当前实例
  Bizinst.findOneAndDelete(query, function (err) {
    if (err) return tools.handleError(res, err);
    return res.json({ status: 'success' })
  });
});

/**
 * @swagger
 *
 * /cmdb/bizs/batch:
 *   delete:
 *     tags:
 *       - 业务实例管理
 *     summary: 批量删除业务实例
 *     description: 批量删除业务实例，只支持根据ID删除
 *     parameters:
 *       - name: ids
 *         description: id数组
 *         in: formData
 *         required: true
 *         type: list
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.delete('/batch', async function (req, res, next) {
  var allInstance = await Bizinst.find({_id: {$in: req.body.ids} })
  for (var i=0; i<allInstance.length; i++) {
    var inst = allInstance[i]
    // 先查出所有的关系实例
    var allRelationInstance = await Bizrelainst.find({ $or: [{ src: inst._id }, { dest: inst._id }] })
    var allRelationInstanceId = allRelationInstance.map(it => it._id)
    // 先删除所有的关系实例
    await Bizrelainst.deleteMany({_id: {$in: allRelationInstanceId}})
    // 删除所有相关实例对关系的引用
    await Bizinst.updateMany({ relations: { $in: allRelationInstanceId } }, { $pull: { relations: { $in: allRelationInstanceId } } })
    await Bizinst.findOneAndDelete({_id: inst._id})
  }
  return res.json({ status: 'success', data: allInstance.length})
})

/**
 * @swagger
 *
 * /cmdb/bizs/{id}:
 *   put:
 *     tags:
 *       - 业务实例管理
 *     summary: 更新业务实例
 *     description: 业务实例详情。属性按照{key1冒号value1, key2冒号value2,...}的形式提交。属性更新是全量更新，否则已存在的属性值会被抹掉，慎用。
 *     parameters:
 *       - name: id
 *         description: 业务实例ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: name
 *         description: 名字
 *         in: formData
 *         required: true
 *         type: string
 *       - name: desc
 *         description: 描述
 *         in: formData
 *         required: false
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.put('/:id', async function (req, res, next) {

  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }
  var beforeUpdate = await Bizinst.findOne(query).populate({ path: 'bizdef', select: '-versions' })
  if (!beforeUpdate) {
    return tools.handleError(res, '业务实例不存在')
  }

  var attrs = []
  beforeUpdate.bizdef.attrs.forEach(a => {
    attrs.push({ key: a.field, value: req.body[a.field] })
  })

  // 包含的实例关系
  var instances = req.body.bizs_instances ? req.body.bizs_instances : []
  instances.forEach(b =>{
    const instance = Instance.findById(b)
    if (!instance) return tools.handleError(res, '业务实例包括的资源不存在');
  })

  var { name, desc } = req.body
  var data = {
    name,
    desc,
    attrs,
    instances,
    updated: Date.now()
  }

  var afterUpdate = await Bizinst.findOneAndUpdate(query, { $set: data }, { new: true });
  // 移除缓存. 缓存内可能以ID和CODE两种方式缓存
  cache.remove(CACHE_INSTANCE, [afterUpdate._id, afterUpdate.code])

  // 计算变更项
  var diffs = changesets.diff(beforeUpdate.attrs.reduce((o, item) => { o.key = item.value; return o }, {}), afterUpdate.attrs.reduce((o, item) => { o[item.key] = item.value; return o }, {}));
  if (diffs.length > 0) {
    // 记录变更人
    if (req.user.is_authenticated) {
      diffs.forEach(d => {
        d.operator = req.user.username
        d.operatorName = req.user.name
      })
    }
    // 资产变更发送到kafka
    kafkaProducer.send([
      {
        topic: settings.KAFKA.ASSET_CHANGE_TOPIC,
        messages: JSON.stringify({ code: afterUpdate.code, diffs: diffs })
      },
      {
        topic: settings.KAFKA.LOG_TOPIC,
        messages: JSON.stringify({
          message: JSON.stringify({
            app: 'cmdb',
            ref: afterUpdate.code,
            account: req.user ? req.user.username : '',
            username: req.user ? req.user.name : '',
            ip: req.ip,
            operation: 'CI项变更',
            api: req.originalUrl,
            extra: JSON.stringify(diffs),
            time: new Date()
          }),
          sendDate: new Date()
        })
      }
    ], (err, result) => {
      log.debug(err, result)
    })
    Bizinst.findOneAndUpdate(query, { $push: { versions: { updated: afterUpdate.updated, diffs } } }, { new: true }, function (err, doc) {
      if (err) return tools.handleError(res, err);
      return res.json({ status: 'success', data: afterUpdate })
    });
  } else {
    return res.json({ status: 'success', data: afterUpdate })
  }
});


/**
 * @swagger
 *
 * /cmdb/bizs/{id}/attrs:
 *   put:
 *     tags:
 *       - 业务实例管理
 *     summary: 批量更新业务实例属性值
 *     description: 批量更新业务实例属性值
 *     parameters:
 *       - name: id
 *         description: 业务实例ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: attrs
 *         description: 属性键值对
 *         in: formData
 *         required: true
 *         type: list
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.put('/:id/attrs', async function (req, res, next) {
  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }
  var beforeUpdate = await Bizinst.findOne(query).populate({ path: 'bizdef', select: '-versions' })
  if (!beforeUpdate) {
    return tools.handleError(res, '业务实例不存在')
  }

  var uAttrs = req.body.attrs
  var afterUpdate = beforeUpdate
  for(var i = 0; i < uAttrs.length; i++) {
    query["attrs.key"] = uAttrs[i].key
    var data = {
      updated: Date.now(),
      "attrs.$.value": uAttrs[i].value
    }
    afterUpdate = await Bizinst.findOneAndUpdate(query, { $set: data }, { new: false });
  }
  
  // 清空缓存
  cache.remove(CACHE_INSTANCE, [beforeUpdate._id, beforeUpdate.code])

  var { versions, ...other } = afterUpdate._doc
  return res.json({ status: 'success', data: other })

});


/**
 * @swagger
 *
 * /cmdb/bizs/{id}/{attr}:
 *   put:
 *     tags:
 *       - 业务实例管理
 *     summary: 更新实例属性值
 *     description: 单个属性更新
 *     parameters:
 *       - name: id
 *         description: 实例ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: attr
 *         description: 属性编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: value
 *         description: 属性值
 *         in: formData
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.put('/:id/:attr', async function (req, res, next) {

  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }
  var beforeUpdate = await Bizinst.findOne(query).populate({ path: 'bizdef', select: '-versions' })
  if (!beforeUpdate) {
    return tools.handleError(res, '业务实例不存在')
  }

  var allAttrs = [...beforeUpdate.bizdef.attrs]

  var forUpdateAttr = allAttrs.find(attr => attr.field === req.params.attr)
  if(!forUpdateAttr) {
    return tools.handleError(res, '属性不存在');
  }

  var data = {
    updated: Date.now(),
    "attrs.$.value": req.body.value
  }
  query["attrs.key"] = req.params.attr

  var afterUpdate = await Bizinst.findOneAndUpdate(query, { $set: data }, { new: true });
  // 清空缓存
  cache.remove(CACHE_INSTANCE, [beforeUpdate._id, beforeUpdate.code])
  var diffs = changesets.diff(beforeUpdate.attrs.reduce((o, item) => { o[item.key] = item.value; return o }, {}), afterUpdate.attrs.reduce((o, item) => { o[item.key] = item.value; return o }, {}));
  if (diffs.length > 0) {
    if (req.user.is_authenticated) {
      diffs.forEach(d => {
        d.operator = req.user.username
        d.operatorName = req.user.name
      })
    }
    kafkaProducer.send([
      {
        topic: settings.KAFKA.ASSET_CHANGE_TOPIC,
        messages: JSON.stringify({ code: afterUpdate.code, diffs: diffs })
      },
      {
        topic: settings.KAFKA.LOG_TOPIC,
        messages: JSON.stringify({
          message: JSON.stringify({
            app: 'cmdb',
            ref: afterUpdate.code,
            account: req.user ? req.user.username : '',
            username: req.user ? req.user.name : '',
            ip: req.ip,
            operation: 'CI项变更',
            api: req.originalUrl,
            extra: JSON.stringify(diffs),
            time: new Date()
          }),
          sendDate: new Date()
        })
      }
    ], (err, result) => {
      if (err) {
        log.error(err)
      }
      log.debug('变更项发往KAFKA结果：', result)
    })
    Bizinst.findOneAndUpdate(query, { $push: { versions: { updated: afterUpdate.updated, diffs } } }, { new: true }, function (err, doc) {
      if (err) {
        log.error(err)
        return tools.handleError(res, err);
      }
      var { versions, ...other } = doc._doc
      return res.json({ status: 'success', data: other })
    });
  } else {
    var { versions, ...other } = afterUpdate._doc
    return res.json({ status: 'success', data: other })
  }

});


/**
 * @swagger
 *
 * /cmdb/bizs/{id}/relations:
 *   get:
 *     tags:
 *       - 业务实例管理
 *     summary: 查询业务实例关联关系
 *     description: 查询业务实例关联关系
 *     parameters:
 *       - name: id
 *         description: 实例ID或编码
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.get('/:id/relations', async function (req, res, next) {
  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }

  var inst = await Bizinst.findOne(query).populate({ path: 'schemadef', select: '-versions' })
  if (!inst) return tools.handleError(res, '资源不存在');

  Bizrelainst.find({ $or: [{ src: inst._id }, { dest: inst._id }] })
    .populate('rela')
    .populate('src')
    .populate('dest')
    .exec((err, docs) => {
      if (err) return tools.handleError(res, err);
      return res.json({ status: 'success', data: docs })
    })
});


/**
 * @swagger
 *
 * /cmdb/bizs/{id}/relations/{rid}:
 *   delete:
 *     tags:
 *       - 业务实例管理
 *     summary: 删除业务关联关系
 *     description: 删除业务关联关系
 *     parameters:
 *       - name: id
 *         description: 实例ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: rid
 *         description: 关联关系实例ID
 *         in: formData
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.delete('/:id/relations/:rid', async function (req, res, next) {
  const relationInstance = await Bizrelainst.findById(req.params.rid).populate('src').populate('dest')
  if (!relationInstance) return tools.handleError(res, '关联关系实例不存在');
  // 移除源实例和目标实例的引用
  await Bizinst.updateMany({ _id: { $in: [relationInstance.src._id, relationInstance.dest._id] } }, { $pull: { relations: { $in: [relationInstance._id] } } })
  cache.remove(CACHE_INSTANCE, [relationInstance.src._id, relationInstance.src.code, relationInstance.dest._id, relationInstance.dest.code])
  // 删除关联关系实例
  Bizrelainst.findByIdAndDelete(req.params.rid, function (err) {
    if (err) return tools.handleError(res, err);
    res.json({ status: 'success' })
  });
});

/**
 * @swagger
 *
 * /cmdb/bizs/{id}/relations/add:
 *   put:
 *     tags:
 *       - 业务实例管理
 *     summary: 添加业务实例关联关系
 *     description: attrs参数以对象结构提交
 *     parameters:
 *       - name: id
 *         description: 实例ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: src
 *         description: 源实例ID
 *         in: formData
 *         required: true
 *         type: string
 *       - name: dest
 *         description: 目标实例ID
 *         in: formData
 *         required: true
 *         type: string
 *       - name: rela
 *         description: 关联关系模型ID
 *         in: formData
 *         required: true
 *         type: string
 *       - name: attrs
 *         description: 属性
 *         in: formData
 *         required: false
 *         type: object
 *       - name: desc
 *         description: 描述
 *         in: formData
 *         required: false
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.put('/:id/relations/add', async function (req, res, next) {
  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }
  var instance = await Bizinst.findOne(query).populate({ path: 'schemadef', select: '-versions' })
  if (!instance) {
    return tools.handleError(res, '业务实例不存在');
  }

  // 只接受5个字段部分
  var { desc, src, dest, rela } = req.body
  var relaInst = await Bizrela.findById(rela)
  if (!relaInst) return tools.handleError(res, '关联关系不存在');
  var srcInst = await Bizinst.findById(src)
  if (!srcInst) return tools.handleError(res, '源业务实例不存在');
  var destInst = await Bizinst.findById(dest)
  if (!destInst) return tools.handleError(res, '目标业务实例不存在');

  var attrs = []
  relaInst.attrs.forEach(a => {
    attrs.push({ key: a.field, value: req.body.attrs[a.field] })
  })

  var data = {
    rela: relaInst._id,
    src: srcInst._id,
    dest: destInst._id,
    desc,
    attrs
  }

  // 创建新的关联关系实例（有更无增）
  await Bizrelainst.updateOne({ rela: relaInst._id, src: srcInst._id, dest: destInst._id }, data, { upsert: true })
  // 拿到新增的关联关系实例
  const r = await Bizrelainst.findOne({ rela: relaInst._id, src: srcInst._id, dest: destInst._id })
  // 源实例要保存关联关系实例的引用
  var srcBeforeUpdate = await Bizinst.findByIdAndUpdate(src, { $addToSet: { relations: r } }, { new: false });
  cache.remove(CACHE_INSTANCE, [srcBeforeUpdate._id, srcBeforeUpdate.code]);
  // var {srcVersions, ...srcData} = srcBeforeUpdate._doc
  // 目标实例要保存关联关系实例的引用
  var destBeforeUpdate = await Bizinst.findByIdAndUpdate(dest, { $addToSet: { relations: r } }, { new: false });
  cache.remove(CACHE_INSTANCE, [destBeforeUpdate._id, destBeforeUpdate.code]);
  // var {destVersions, ...destData} = destBeforeUpdate._doc
  // 保存目标实例变更 TODO: 暂时不保存变更
  // await Instance.findByIdAndUpdate(dest, {$push: {versions:destData}}, { new: true })
  // 保存源实例变更
  // Instance.findByIdAndUpdate(req.params.id, {$push: {versions:srcData}}, { new: true }, function (err, doc) {
  //   if (err) return tools.handleError(res, err);
  //   res.json({ status: 'success', data: doc })
  // });
  res.json({ status: 'success' })
});


/**
 * @swagger
 *
 * /cmdb/bizs/bizdef/{bizdef}:
 *   get:
 *     tags:
 *       - 业务实例管理
 *     summary: 业务模型实例（不分页）
 *     description: 根据编码、名字、属性值模糊查询。支持根据属性key:value精确查询s_foo=bar
 *     parameters:
 *       - name: bizdef
 *         description: 业务模型ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: s_kw
 *         description: 关键字
 *         in: query
 *         required: false
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.get('/bizdef/:bizdef', async function (req, res, next) {

  var isById = tools.isValidObjectId(req.params.bizdef)
  var bizdefQuery = isById ? { _id: req.params.bizdef } : { code: req.params.bizdef }
  var bizdef = await Bizdef.findOne(bizdefQuery)
  if (!bizdef) {
    return tools.handleError(res, '业务模型不存在')
  }

  var keyword = req.query.s_kw || ""
  keyword = keyword.replace(/(^\s*)|(\s*$)/g, '')
  var query = getSearchQuery(keyword)
  query.bizdef = bizdef._id
  // 支持根据属性key:value精确查询
  Object.keys(req.query).filter(key => key !== 's_kw' && key.indexOf('s_') > -1).forEach(queryKey => {
    query.attrs = { "$elemMatch": { "key": queryKey.substring(2), value: req.query[queryKey] } }
  })

  Bizinst.find(query, { versions: 0 })
    .populate({ path: 'bizdef', select: '-versions' })
    .exec((err, docs) => {
      if (err) return tools.handleError(res, err);
      return res.json({ status: 'success', data: docs })
    })
});


/**
 * @swagger
 *
 * /cmdb/bizs/{id}/topo:
 *   get:
 *     tags:
 *       - 业务实例管理
 *     summary: 查询业务实例拓扑
 *     description: 查询业务实例拓扑
 *     parameters:
 *       - name: id
 *         description: 业务实例ID或编码
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.get('/:id/topo', async function (req, res, next) {
  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }
  var instance = await Bizinst.findOne(query).populate({ path: 'bizdef', select: '-versions' }).populate({ path: 'instances', select: '-versions' })
  if (!instance) {
    return tools.handleError(res, '业务实例不存在');
  }

  var instanceId = instance._id.toString()
  let bizdef = await Bizdef.findOne({ _id: instance.bizdef._id })
  let icon = bizdef.icon

  var relations = await Bizrelainst.find({ $or: [{ src: instance._id }, { dest: instance._id }] })
    .populate('rela')
    .populate('src')
    .populate('dest')

  var nodes = []
  var edges = []
  nodes.push({ id: instance.code, _id: instance._id, label: instance.name, icon: icon ? icon : '' })

  //instances
  let instances = instance.instances
  for (var i = 0; i < instances.length; i++) {
    var r = instances[i];
    let icon = await SchemaIcon.findOne({ schemadef: r.schemadef._id })
    nodes.push({ id: r.code, _id: r._id, label: r.name, icon: icon ? icon.url : '', type:'asset'})
    edges.push({ from: instance.code, to: r.code, arrows: 'from', font: { multi: true }, label: '<b>资源组成</b>\n' })
  }

  // 所有的关系
  for (var i = 0; i < relations.length; i++) {
    var r = relations[i];
    var srcId = r.src._id.toString();
    var destId = r.dest._id.toString();
    if (instanceId == srcId) {
      let destBizdef = await Bizdef.findOne({ _id: r.dest.bizdef._id })
      // 排重
      if(!nodes.some(e => e.id === r.dest.code)){
        nodes.push({ id: r.dest.code, _id: r.dest._id, label: r.dest.name, icon: destBizdef ? destBizdef.icon : '' })
      }

      let relationType = await RelationType.findById(r.rela.type)
      var label = '<b>' + relationType.destdesc + '</b>\n'
      if (r.attrs) {
        r.attrs.forEach(attr => {
          var attrDef = r.rela.attrs.find(a => a.field == attr.key)
          label += (attrDef ? attrDef.name : attr.key) + ': ' + attr.value + '\n'
        })
      }
      edges.push({ from: r.src.code, to: r.dest.code, arrows: 'from', font: { multi: true }, label: label })
    } else {
      let srcBizdef = await Bizdef.findOne({ _id: r.src.bizdef._id })
      // 排重
      if(!nodes.some(e => e.id === r.src.code)){
        nodes.push({ id: r.src.code, _id: r.src._id, label: r.src.name, icon: srcBizdef ? srcBizdef.icon : '' });
      }
      let relationType = await RelationType.findById(r.rela.type)
      var label = '<b>' + relationType.srcdesc + '</b>\n'
      if (r.attrs) {
        r.attrs.forEach(attr => {
          var attrDef = r.rela.attrs.find(a => a.field == attr.key)
          label += (attrDef ? attrDef.name : attr.key) + ': ' + attr.value + '\n'
        })
      }
      edges.push({ from: r.src.code, to: r.dest.code, arrows: 'to', font: { multi: true }, label: label });
    }
  }

  var allAttrs = [ ...instance.bizdef.attrs]
  var foreigns = allAttrs.filter(attr => attr.dtype === 'foreign')
  for (var i = 0; i < foreigns.length; i++) {
    var attr = instance.attrs.find(it => it.key === foreigns[i].field && !!it.value)
    if (attr) {
      var foreignInst = await Bizinst.findOne({ code: attr.value }, { _id: 1, code: 1, name: 1, bizdef: 1 })
      let foreignBizdef = await Bizdef.findOne({ _id: foreignInst.bizdef._id })
      // 排重
      if(!nodes.some(e => e.id === foreignInst.code)){
        nodes.push({ id: foreignInst.code, _id: foreignInst._id, label: foreignInst.name, icon: foreignBizdef ? foreignBizdef.icon : '' })
      }
      edges.push({ from: instance.code, to: foreignInst.code, arrows: 'to', font: { multi: true }, label: '<b>外键</b>\n' })
    }
  }

  return res.json({ status: 'success', data: { nodes, edges } })
});

/**
 * @swagger
 *
 * /cmdb/bizs/bizdef/{bizdefId}/export:
 *   get:
 *     tags:
 *       - 业务实例管理
 *     summary: 导出对应的业务模型数据
 *     description: 对应的业务模型数据导出
 *     parameters:
 *       - name: bizdefId
 *         description: 业务模型ID
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.get('/bizdef/:bizdefId/export', async function (req, res, next) {
  var keyword = req.query.s_kw || ""
  keyword = keyword.replace(/(^\s*)|(\s*$)/g, '')
  var query = getSearchQuery(keyword)
  var results = await Bizinst.find(query, { relations: 0, versions: 0 }).populate({ path: 'bizdef', select: '-versions' })

  // 按模型分组
  var groups = {}
  results.map(doc => {
    groups.hasOwnProperty(doc.bizdef._id) ? groups[doc.bizdef._id].push(doc) : (groups[doc.bizdef._id] = [doc])
  })
  var workbook = new Excel.Workbook();
  groups = Object.values(groups)
  for (var i = 0; i < groups.length; i++) {
    var docs = groups[i]
    var sheet = workbook.addWorksheet(docs[0].bizdef.name, { views: [{ state: 'frozen', xSplit: 1, ySplit: 1 }] });
    var columns = [{ header: '名称', key: 'name', width: 24 }, { header: '唯一标识', key: 'code', width: 16 }]

    docs[0].bizdef.attrs.forEach(attr => {
      columns.push({ header: attr.name, key: attr.field, width: 16 })
    })
    sheet.columns = columns;
    docs.forEach(doc => {
      var row = { ...doc.attrs.reduce((o, item) => { o[item.key] = item.value; return o }, {}), name: doc.name, code: doc.code }
      sheet.addRow(row);
    })

    sheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' }
    sheet.getRow(1).font = { bold: true }
  }
  var tmpDir = settings.TMP_PATH ? settings.TMP_PATH : os.tmpdir()
  var fileName = uuid.v1() + '.xlsx'
  var tmpFile = path.join(tmpDir, fileName)
  await workbook.xlsx.writeFile(tmpFile);

  res.set({
    "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "Content-Disposition": "attachment; filename=" + fileName,
    "Access-Control-Expose-Headers": "Content-Disposition"
  });
  fs.createReadStream(tmpFile).pipe(res);
}) 

module.exports = router;
